package org.amse.korovkin.ciphering.model.cipher.util;

import java.util.Arrays;

/**
 *
 * @author Mikhail Korovkin (maverick3089@yandex.ru)
 */
public class Binary {
    // danger!!! reverse order
    private final boolean[] values;
    private byte length;

    private static final int MAX_RADIX = 32;

    public Binary(int a) {
        if (a < 0) {
            throw new IllegalArgumentException("Binary value can not be negative: " + a);
        }
        values = new boolean[MAX_RADIX];
        int charPos = 0;

        byte lengthValue = 0;
        do {
            values[charPos++] = (a & 1) == 1;
            if ((a & 1) == 1) {
                lengthValue = (byte) charPos;
            }
            a >>= 1;
        } while (a != 0);
        length = lengthValue;
    }

    public byte getLength() {
        return length;
    }

    @Override
    public Binary clone() {
        return new Binary(this.intValue());
    }

    public int intValue() {
        int result = 0;
        int cur = 1;
        for (int i = 0; i < length; i++) {
            if (values[i]) {
                result +=cur;
            }
            cur <<= 1;
        }
        return result;
    }

    public int getModule() {
        int result = 1;
        if (length == 1 || length == 0) {
            return 0;
        }
        for (int i = 1; i < length; i++) {
            result *= 2;
        }
        return result;
    }

    public Binary mod(Binary N) {
        Binary result = this.clone();
        while (result.length >= N.length) {
            result = result.sub(N.shiftLeft(result.length - N.length, true));
        }
        return result;
    }

    public Binary shiftLeft(int radix) {
        return shiftLeft(radix, true);
    }

    public Binary shiftLeft(int radix, boolean changeLength) {
        if (radix < 0) {
            throw new IllegalArgumentException("Wrong radix value: " + radix);
        } else if (radix == 0) {
            return this.clone();
        } else if (radix > MAX_RADIX) {
            return shiftLeft(MAX_RADIX, changeLength);
        } else {
            Binary clone = this.clone();
            for (int i = clone.length - 1; i >= 0; i--) {
                if (i + radix < MAX_RADIX) {
                    if (i + radix < clone.length || changeLength) {
                        clone.values[i + radix] = clone.values[i];
                    }
                }
            }
            for (int i = 0; i < radix; i++) {
                clone.values[i] = false;
            }
            clone.updateLength();
            return clone;
        }
    }

    public Binary sub(Binary b) {
        Binary result = this.clone();
        byte maxLength = result.length >= b.length ? result.length : b.length;
        for (byte i = 0; i < maxLength; i++) {
            result.values[i] = (result.values[i] ^ b.values[i]);
        }
        result.updateLength();
        return result;
    }

    public Binary add(Binary b) {
        return this.sub(b);
    }

    public Binary mult(Binary b) {
        Binary result = new Binary(0);
        for (int i = 0; i < b.length; i++) {
            if (b.values[i]) {
                result = result.add(this.shiftLeft(i));
            }
        }
        return result;
    }

//    public Binary div(Binary b) {
//        return this.mult(BinaryUtils.getInverse(b, b))
//    }

    private void updateLength() {
        for (byte i = MAX_RADIX - 1; i >= 0; i--) {
            if (values[i]) {
                length = (byte) (i + 1);
                return;
            }
        }
        length = 0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = length - 1; i >= 0; i--) {
            sb.append(values[i] ? "1" : "0");
        }
        if (sb.length() == 0) {
            sb.append("0");
        }
        sb.append("(").append(this.intValue()).append(")");
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        return (o instanceof Binary) && ((Binary)o).toString().equals(this.toString());
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 71 * hash + Arrays.hashCode(this.values);
        hash = 71 * hash + this.length;
        return hash;
    }

    public static void main(String[] args) {
        Binary b = new Binary(3);
        System.out.println(b.mult(b).add(new Binary(2)).mod(new Binary(7)));
        System.out.println(b.mult(b).mod(new Binary(7)).add(new Binary(2)));
//        System.out.println(new Binary(Binary.binaryMod(0, 283)));
//        System.out.println(new Binary(Binary.binaryMod(256, 283)));
    }
}
